/*
** myinetd.c -- 
*/

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <syslog.h>
#include <sys/stat.h>

#define BACKLOG 10            /* how many pending connections queue will hold */
#define MAXLINE 100           /* tamanho maximo da entrada de dados */
#define MAXFD 64              /* valor limite dos descritores */
#define PSIZE 300             /* tamanho maximo de uma linha do arquivo conf */
#define STRTOKENS " \t\n"     /* token para o strtok*/
#define LOGPATH "/log.083681"

/* define protocolo */
typedef enum _protocol {
  TCP,
  UDP
} Protocol;

/* define wait/NOWAIT */
typedef enum _waitNOWAIT {
  YESWAIT,
  NOWAIT
} WaitNOWAIT;

/* define um servico */
typedef struct _service {
  int sockfd;
  char name[MAXLINE];
  int port;
  int socktype;
  Protocol prot;
  WaitNOWAIT wnw;
  char program[MAXLINE];
  char *args[MAXLINE];
  int pid;
} Service;

void daemon_init(void) {
  int i;
    
  /* mata 1o pai */
  if (fork())
    exit(0);               
    
  /* seta 1o filho como lider */
  setsid();                   
  signal(SIGHUP, SIG_IGN);    
    
  /* mata 1o filho */
  if (fork())
    exit(0);              
    
  /* muda o diretorio de trabalho */
  chdir("/tmp"); 
  umask(0);     
    
  /* fecha todos descritores de arquivos */
  for (i = 0; i < MAXFD; i++) 
    close(i);
 
  return;
}

/* instancia um socket */
int InstanciaSocket(const Service srvc) {

  int sockfd;
  struct sockaddr_in my_addr;   

  if ((sockfd = socket(AF_INET, srvc.socktype, 0)) == -1) 
    exit(1);

  my_addr.sin_family = AF_INET;           
  my_addr.sin_port = htons(srvc.port);    
  my_addr.sin_addr.s_addr = INADDR_ANY; 
  bzero(&(my_addr.sin_zero), 8);        

  if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) 
    exit(1);

  /* listen para servidor TCP */
  if (srvc.prot == TCP) {
    if (listen(sockfd, BACKLOG) == -1) 
      exit(1);

  return sockfd;
}

/* le o arquivo de servicos */
void readServices(char *conf_path, Service services[MAXFD], int *nfds) {
  int j, k, comment;
  FILE *conf;
  char param[PSIZE], *strtmp, buf[PSIZE];
  Service socktmp;


  /* abre o arquivo de configuracao */
  if ((conf = fopen(conf_path, "r")) == NULL)
    exit(1);

  /* instancia os sockets */
  *nfds = 0;

  while (fgets(param, PSIZE, conf)) {

    /* quebra a string */
    strtmp = strtok(param, STRTOKENS);

    /* comentarios */
    if ((strtmp == NULL) || (strtmp[0] == '#')) {
      continue;
    }

    /* name */
    strcpy(socktmp.name, strtmp);

    /* port */
    socktmp.port = atoi(strtok(NULL, STRTOKENS));


    /* socktype */
    strtmp = strtok(NULL, STRTOKENS);

    if (strcmp(strtmp, "stream") == 0)
      socktmp.socktype = SOCK_STREAM;
    
    else if (strcmp(strtmp, "dgram") == 0)
      socktmp.socktype = SOCK_DGRAM;
    
    else 
      continue;
    

    /* protocol */
    strtmp = strtok(NULL, STRTOKENS);

    if (strcmp(strtmp, "tcp") == 0)
      socktmp.prot = TCP;

    else if (strcmp(strtmp, "udp") == 0) 
      socktmp.prot = UDP;

    else  
      continue;
    

    /* wait/nowait */
    strtmp = strtok(NULL, STRTOKENS);
    if (strcmp(strtmp, "wait") == 0)
      socktmp.wnw = YESWAIT;
   
    else if (strcmp(strtmp, "nowait") == 0)
      socktmp.wnw = NOWAIT;
    
    else 
      continue;


    /* verifica consistencia */
    if (!(  /* if not */
	  /* tcp */
	  ((socktmp.socktype == SOCK_STREAM) &&
	   (socktmp.prot == TCP) &&
	   (socktmp.wnw == NOWAIT))
	  || /* or */
	  /* udp */
	  ((socktmp.socktype == SOCK_DGRAM) &&
	   (socktmp.prot == UDP) &&
	   (socktmp.wnw == YESWAIT))
	    )) {

      // colocar log
      continue;
    }


    /* program */
    strcpy(socktmp.program, strtok(NULL, STRTOKENS));

    /* args */
    comment = 0;
    k = 0;
    while (((strtmp = strtok(NULL, STRTOKENS)) != NULL) && (! comment)) {
      if (strtmp[0] != '#') {
	socktmp.args[k] = (char*)malloc(sizeof(char)*(strlen(strtmp)+1));
	strcpy(socktmp.args[k], strtmp);
	k++;
      } else {
	comment = 1;
      }
    }
    socktmp.args[k] = (char*)NULL;

    /* sockfd */
    socktmp.sockfd = InstanciaSocket(socktmp);
    socktmp.pid = 0;

    services[*nfds] = socktmp;

    /* coloca no log */
    sprintf(buf, 
	    ">> servico incluido: %d %s %d %d %d %d %s ",
	    services[*nfds].sockfd,
	    services[*nfds].name,
	    services[*nfds].port,
	    services[*nfds].socktype,
	    services[*nfds].prot,
	    services[*nfds].wnw,
	    services[*nfds].program);
    for (j = 0; j < k; j++) {
      sprintf(&buf[strlen(buf)], "%s ", services[*nfds].args[j]);
    }
    // colocar buf no log

    (*nfds)++;
  }
}


/* executa o servico */
void executeService(const Service services[MAXFD], const int nfds,
		    const int srvcindex, const int new_fd) {
  int i;

  /* fecha os sockets que nao forem do servico a ser rodado */
  for (i = 0; i < nfds; i++) 
    if (services[i].sockfd != new_fd) 
      close(services[i].sockfd);
  
  /* duplica o socket conectado */
  if (dup2(new_fd, STDIN_FILENO) == -1) {
    syslog(LOG_ERR|LOG_USER, "== dup2 in: %s\n", strerror(errno));
    exit(1);
  }
  if (dup2(new_fd, STDOUT_FILENO) == -1) {
    syslog(LOG_ERR|LOG_USER, "== dup2 out: %s\n", strerror(errno));
    exit(1);
  }
  if (dup2(new_fd, STDERR_FILENO) == -1) {
    syslog(LOG_ERR|LOG_USER, "== dup2 err: %s\n", strerror(errno));
    exit(1);
  }
    
  close(new_fd);

  /* executa o servico */
  if (execv(services[srvcindex].program, services[srvcindex].args) == -1) {
    syslog(LOG_ERR|LOG_USER, "== execv: %s\n", strerror(errno));
    exit(1);
  }

  return;
}

/* funcao principal */
int main(int argc, char * argv[]) {

  int nfds, maxfds, new_fd, childpid, i, k;
  unsigned int sin_size;
  Service services[MAXFD];
  fd_set readfds;
  unsigned int id;
  struct sockaddr_in their_addr;  /* connector's address information */
  char buf[MAXLINE];
    
  /* verifica uso */
  if (argc != 2) {
    fprintf(stderr, "usage: myinetd myinetd.conf\n");
    exit(1);
  }

  daemon_init(argv[0]);

  /* inicializa os servicos */
  readServices(argv[1], services, &nfds);

  /* calcula o maior fd */
  maxfds = 0;
  for (i = 0; i < nfds; i++) 
    if (services[i].sockfd > maxfds) 
      maxfds = services[i].sockfd;
  
  maxfds++;

  id = 0;

  while(1) { 

    /* inicializa os fds do select */
    FD_ZERO(&readfds);
    for (i = 0; i < nfds; i++)
      if (services[i].pid == 0)
	FD_SET(services[i].sockfd, &readfds);

    sin_size = sizeof(struct sockaddr_in);

    /* espera pedido */
    if (select(maxfds, &readfds, NULL, NULL, NULL) < 0) 
      exit(1);

    /* atende chamada */
    i = 0;
    while (!FD_ISSET(services[i].sockfd, &readfds))
      i++;

    id++;

    /* requisicao TCP */ 
    if (services[i].prot == TCP) {  
      if ((new_fd = accept(services[i].sockfd,
			   (struct sockaddr *)&their_addr, &sin_size)) == -1) 
	exit(1);

      if (!fork()) {  /* processo filho executa o servico */
	executeService(services, nfds, i, new_fd);
	exit(0);
      }

      // colocar log com servico, cliente e porta

      close(new_fd);

    } 

    /* requisicao UDP */
    else { 
      /* acha o cliente */
      if (recvfrom(services[i].sockfd, buf, MAXLINE, MSG_PEEK, 
		   (struct sockaddr *)&their_addr, &sin_size) == -1) {
	syslog(LOG_ERR|LOG_USER, "== recvfrom: %s\n", strerror(errno));
	exit(1);
      }

      if (! (services[i].pid = fork())) {  /* processo filho executa o servico */
	executeService(services, nfds, i, services[i].sockfd);
	exit(1);
      }

      // colocar log com servico, cliente e porta
    }


    /* limpa processos filho */
    while ((childpid = waitpid(-1, NULL, WNOHANG)) > 0) {
      for (k = 0; k < nfds; k++) {
	/* tira o monopolio do filho finalizado */
	if (services[k].pid == childpid) {
	  services[k].pid = 0;
	}
      }
    }
  }

  return 0;
}

   
